Cross-Platform Coroutines in C++ by George F. Frazier Example 1: class CCoroutine { public: CCoroutine(); virtual ~CCoroutine(); static CCoroutine *initialize(); virtual void go() = 0; virtual void resume(CCoroutine *next) = 0; }; Example 2: void CTargetPlatformCoroutine::resume(CCoroutine *other) { if (other == this) return; assert(other->state_->fiber_); SwitchToFiber(other->state_->fiber_); } Example 3: pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); pthread_create(&state_->thread_, &attr, crstart, this->state_); pthread_attr_destroy(&attr); Example 4: static void *crstart(void *thunk) { CCoroutineState *s = (CCoroutineState *) thunk; s->waitToGo(); s->parent_->go(); assert(0); return 0; } Example 5: void CTargetPlatformCoroutine::resume(CCoroutine *other) { if (other == this) return; // Tell the other guy to go. assert(other->state_); other->state_->signalToGo(); // Wait until someone tells us to go. assert(state_); state_->waitToGo(); } Example 6: void PrintRoutine::go() { int j; while (true) { for (j = 0; j < k; j++) cout << Word1[j]; resume(rspc); for (j = k - 1; j >= 0; j--) cout << Word1[j]; resume(rspc); } } Listing One #ifndef _CWinNTCoroutine_h_ #define _CWinNTCoroutine_h_ ////////////////////////////////////////// // CWinNTCoroutine.h #ifdef _WIN32 #ifdef _COROUTINE_DLL #define ExportStatus _declspec(dllexport) #else #define ExportStatus _declspec(dllimport) #endif #else #define ExportStatus #endif #include ///////////////////////////////////////////////////////// class ExportStatus CTargetPlatformCoroutine : public CCoroutine { public: CTargetPlatformCoroutine(unsigned stackSizeInBytes = DEFSTACKSIZE); virtual ~CTargetPlatformCoroutine(); virtual void go(){assert(0);} virtual void resume(CCoroutine *next); }; #endif ///////////////////////////////////////////////////// // CWinNTCoroutine.cpp ///////////////////////////////////////////////////// // CCoroutineNT.cpp // NT implementation of Coroutine interface. #if (!defined(_WINNT) || !defined(_WIN32_WINNT)) #error "This module is only for Windows NT." #endif #define WIN32_LEAN_AND_MEAN #include #include #include "CCoroutine.h" #include "CWinNTCoroutine.h" /////////////////////////////////////////////////////////// class CCoroutineState { public: CCoroutineState() : fiber_(0) {} LPVOID fiber_; }; static void WINAPI startCoroutine(PVOID lpParameter) { CCoroutine *c = (CCoroutine *) lpParameter; c->go(); assert(0); } /////////////////////////////////////////////////////////// CTargetPlatformCoroutine::CTargetPlatformCoroutine(unsigned stackSizeInBytes) : CCoroutine(INITCOROUTINE) { state_ = new CCoroutineState(); if (stackSizeInBytes == INITCOROUTINE) return; state_->fiber_ = CreateFiber(0, startCoroutine, this); assert(state_->fiber_); } CTargetPlatformCoroutine::~CTargetPlatformCoroutine() { assert(state_->fiber_); DeleteFiber(state_->fiber_); state_->fiber_ = 0; delete state_; } void CTargetPlatformCoroutine::resume(CCoroutine *other) { if (other == this) return; assert(other->state_->fiber_); SwitchToFiber(other->state_->fiber_); } ///////////////////////////////////////////////////////// // This can't be pure virtual because it is static CCoroutine *CCoroutine::initialize() { CCoroutine *thisCoroutine = new CTargetPlatformCoroutine(); LPVOID thisFiber = ConvertThreadToFiber(thisCoroutine); assert(thisFiber != 0); thisCoroutine->state_->fiber_ = thisFiber; return thisCoroutine; } Listing Two //////////////////////////////////////////// // CSolarisCoroutine.h //////////////////////////////////////////// #ifndef _CSolarisCoroutine_h_#define _CSolarisCoroutine_h_#include class CTargetPlatformCoroutine : public CCoroutine { public: CTargetPlatformCoroutine(unsigned stackSizeInBytes = DEFSTACKSIZE); virtual ~CTargetPlatformCoroutine(); virtual void go() {assert(0);} virtual void resume(CCoroutine *next); }; #endif //////////////////////////////////////////// // CSolarisCoroutine.cpp //////////////////////////////////////////// #include "CCoroutine.h" #include "CSolarisCoroutine.h" #include #include //////////////////////////////////////////////// class CCoroutineState { public: CCoroutineState(CCoroutine *parent) : parent_(parent), go_(false) { pthread_mutex_init(&go_mutex_, 0); pthread_cond_init(&go_cond_, 0); } CCoroutine *parent_; pthread_t thread_; int go_; pthread_mutex_t go_mutex_; pthread_cond_t go_cond_; inline void waitToGo() { pthread_mutex_lock(&go_mutex_); while (!go_) pthread_cond_wait(&go_cond_, &go_mutex_); go_ = false; pthread_mutex_unlock(&go_mutex_); } inline void signalToGo() { // Tell the other guy to go. pthread_mutex_lock(&go_mutex_); go_ = true; pthread_cond_signal(&go_cond_); pthread_mutex_unlock(&go_mutex_); } }; static void *crstart(void *thunk) { CCoroutineState *s = (CCoroutineState *) thunk; s->waitToGo(); s->parent_->go(); assert(0); return 0; } ///////////////////////////////////////////////////////////// CTargetPlatformCoroutine::CTargetPlatformCoroutine(unsigned requestedStackSizeInBytes) : CCoroutine(INITCOROUTINE) { state_ = new CCoroutineState(this); if (requestedStackSizeInBytes == INITCOROUTINE) return; pthread_attr_t attr; pthread_attr_init(&attr); #ifdef _POSIX_THREAD_ATTR_STACKSIZE size_t stackSize = 8192; if (requestedStackSizeInBytes != DEFSTACKSIZE) stackSize = requestedStackSizeInBytes; #ifdef PTHREAD_STACK_MIN // Solaris is missing this. if (stackSize < PTHREAD_STACK_MIN) stackSize = PTHREAD_STACK_MIN; #endif pthread_attr_setstacksize(&attr, stackSize); #endif pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); pthread_create(&state_->thread_, &attr, crstart, this->state_); pthread_attr_destroy(&attr); } CTargetPlatformCoroutine::~CTargetPlatformCoroutine() { state_ = 0; } void CTargetPlatformCoroutine::resume(CCoroutine *other) { if (other == this) return; // Tell the other guy to go. assert(other->state_); other->state_->signalToGo(); // Wait until someone tells us to go. assert(state_); state_->waitToGo(); } /////////////////////////////////////////////////////////// CCoroutine *CCoroutine::initialize() { return new CTargetPlatformCoroutine(); } Listing Three // oddword.cpp : Defines the entry point for the console application. #include #include #include "CCoroutine.h" #ifdef _WIN32 #include "CWinNTCoroutine.h" #else #include "CSolarisCoroutine.h" #endif CTargetPlatformCoroutine *rwrd; CTargetPlatformCoroutine *rspc; CTargetPlatformCoroutine *prtn; std::string InputString; int InputPointer = 0; int k; char Word1[20]; class ReadWord : public CTargetPlatformCoroutine { public: ReadWord() : CTargetPlatformCoroutine() {this->initialize();} virtual void go(); }; class ReadSpace : public CTargetPlatformCoroutine { public: ReadSpace() : CTargetPlatformCoroutine() {this->initialize();} virtual void go(); }; class PrintRoutine : public CTargetPlatformCoroutine { public: PrintRoutine() : CTargetPlatformCoroutine() {this->initialize();} virtual void go(); }; void ReadWord::go() { char ch; while (true) { k = 0; memset(Word1, 0, 20); ch = InputString[InputPointer]; while (ch != ' ' && ch != '.' && k < 20) { Word1[k] = ch; k++; InputPointer++; ch = InputString[InputPointer]; } resume(prtn); } } void ReadSpace::go() { while (true) { bool bSawSpace = false; if (InputPointer >= InputString.length()) return; // assume that this thing ends with '.' while (InputString[InputPointer] == ' ') { InputPointer++; bSawSpace = true; } if (InputString[InputPointer] == '.') { cout << "." << endl; exit(0); } else { if (bSawSpace) cout << " "; resume(rwrd); } } } void PrintRoutine::go() { int j; while (true) { for (j = 0; j < k; j++) cout << Word1[j]; resume(rspc); for (j = k - 1; j >= 0; j--) cout << Word1[j]; resume(rspc); } } // Let the user enter a string on the command line. Store it in memory in // the variable InputString then read characters one at a time from // InputString to solve the odd word problem. Make sure to enclose your // command-line argument in double quotes int main(int argc, char* argv[]) { if (argc != 2) { InputString = "whats the matter with Kansas."; } else { InputString = argv[1]; } rwrd = new ReadWord(); rspc = new ReadSpace(); prtn = new PrintRoutine(); rspc->go(); return 0; } 7